package dfsketches.gr3d;

import java.util.Hashtable;
import java.util.HashSet;

//import dfsketches.bicycles.Hashtable;

public final class Instance {

	private static int N = 0;
	private int n = 0;

	private Model model;
	private Matrix3D parentTransform;
	private Matrix3D selfTransform;
	private Matrix3D complexTransform;
	private Hashtable<Bone, Matrix3D> boneTrans;
	private AnimationInfo aniInf;
//	private Bone referredTo;
	private Instance parent;
	private HashSet<Instance> subInstances;
	
	private Triangle temp_triangle;
	private Matrix3D temp_matrix;
	private Matrix3D temp_matrix2;
	
	public Instance(Model m) {
		model = m;
		
		selfTransform = Matrix3D.getUniteMatrix();
		parentTransform = Matrix3D.getUniteMatrix();
		complexTransform = Matrix3D.getUniteMatrix();
		
		aniInf = new AnimationInfo(model.animCollection.get("none"), 0f, 0f);
		
		n = N++;
		parent = null;
		subInstances = new HashSet<Instance>();
//		referredTo = null;

		temp_matrix = new Matrix3D();
		temp_matrix2 = new Matrix3D();
		boneTrans = new Hashtable<Bone, Matrix3D>();
		putBoneMatricesRecursive(model.mainBone);
		
		temp_triangle = new Triangle();
	}
	
	private void putBoneMatricesRecursive(Bone b) {
		boneTrans.put(b, new Matrix3D());
		for (Bone sb : b.subBones) {
			putBoneMatricesRecursive(sb);
		}
	}

	public void reanimation(Projector p) {
		aniInf.update();
		temp_matrix2.copy(complexTransform);
		temp_matrix2.multiplyRight(p.getMatrix());
		reanimateBone(model.mainBone, temp_matrix2);
	}
	
	private void reanimateBone(Bone b, Matrix3D parentTransform) {
		aniInf.getTransformation(b, temp_matrix);
		temp_matrix.multiplyRight(parentTransform);
		(parentTransform = boneTrans.get(b)).copy(temp_matrix);
		for (Bone sb : b.subBones) {
			reanimateBone(sb, parentTransform);
		}
	}
	
	public void playAnimation(String name, float start, float speed) {
		Animation anim = model.animCollection.get(name);
		if (anim == null) anim = model.animCollection.get("none");
		aniInf = new AnimationInfo(anim, start, speed);
	}
	
	public void changeAnimationSpeed(float newSpeed) {
		aniInf.changeAnimationSpeed(newSpeed);
	}
	
	public void stopAnimation() {
		aniInf = new AnimationInfo(model.animCollection.get("none"), 0f, 0f);
	}
	
	public long getInstanceNumber() {
		return n;
	}
	
	public void addSubinstance(Instance inst) {
		subInstances.add(inst);
		inst.parent = this;
		inst.selfTransform.multiplyRight(selfTransform);
	}
	
	public void removeSubinstance(Instance inst) {
		subInstances.remove(inst);
		inst.parent = null;
	}
	
	private void calculateComplexMatrix() {
		complexTransform.copy(selfTransform);
		complexTransform.multiplyRight(parentTransform);
	}
	
	private void parentApply(Matrix3D m) {
		parentTransform = parent.complexTransform;
		calculateComplexMatrix();
		for (Instance inst : subInstances) {
			inst.parentApply(m);
		}
	}
	
	public void apply(Matrix3D m) {
		selfTransform.multiplyRight(m);
		calculateComplexMatrix();
		for (Instance inst : subInstances) {
			inst.parentApply(m);
		}
	}
	
	public void render(Projector p, Cutter c, Renderer r) {
		applyTransformations(model.mainBone, p);
		
		for (Triangle tri : model.triangles) {
			temp_triangle.tcopy(tri);
			c.render(temp_triangle, p);
		}
		
	}

	private void applyTransformations(Bone b, Projector p) {
		Matrix3D m = boneTrans.get(b);
		
		for (Vertex v : b.vertices) {
			v.tApply(m);
			p.project(v);
		}

		for (Bone sb : b.subBones) {
			applyTransformations(sb, p);
		}
	}

	public float isClicked(int x, int y, Projector proj) {
		Triangle localTriangle = new Triangle();
		final float dist = 1f;
		Vector3D base = new Vector3D(0f, 0f, 0f);
		Vector3D direction = new Vector3D(proj.getUnscaledX(x), proj.getUnscaledY(y), dist);

		applyTransformations(model.mainBone, proj);

		float minDist = Float.MAX_VALUE;
		for (Triangle tri : model.triangles) {
			localTriangle.tcopy(tri);
			proj.project(localTriangle);
			
			Vertex a = localTriangle.a;
			Vertex b = localTriangle.b;
			Vertex c = localTriangle.c;
			float abv = (b.px - a.px) * (y - a.py) - (b.py - a.py) * (x - a.px);
			float bcv = (c.px - b.px) * (y - b.py) - (c.py - b.py) * (x - b.px);
			float cav = (a.px - c.px) * (y - c.py) - (a.py - c.py) * (x - c.px);
			if ((abv >= 0 && bcv >= 0 && cav >= 0) || (abv <= 0 && bcv <= 0 && cav <= 0)) {
				Vector3D normal = tS(a, b, c);
				float r = Vector3D.scalarMultiply(normal, new Vector3D(base, a)) /
						Vector3D.scalarMultiply(normal, direction);
				if (minDist > r && r > dist) {
					minDist = r;
				}
			}
		}
		return minDist;
	}
	
	private Vector3D tS(Vector3D a, Vector3D b, Vector3D c) {
		return Vector3D.vectorMultiply(new Vector3D(b, a), new Vector3D(c, a));
	}

	public void setMatrix(Matrix3D translationMatrix) {
		selfTransform.copy(translationMatrix);
		calculateComplexMatrix();
		Matrix3D un = Matrix3D.getUniteMatrix();
		for (Instance inst : subInstances) {
			inst.parentApply(un);
		}
	}
}